package com.example.opencvtest;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfByte;
import org.opencv.core.MatOfPoint;
import org.opencv.core.MatOfPoint2f;
import org.opencv.core.Point;
import org.opencv.core.RotatedRect;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

@SpringBootApplication
@RestController
public class OpencvTestApplication {
    protected final Logger logger = LoggerFactory.getLogger(getClass());

    private static long timestamp = 0;

    public static void main(String[] args) {
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
        SpringApplication.run(OpencvTestApplication.class, args);
    }

    @PostMapping("/black")
    public void clearBackGround(MultipartFile file) {
        // 读取字节
        byte[] bytes = null;
        try (InputStream inputStream = file.getInputStream(); FileOutputStream fileOutputStream = new FileOutputStream(
                file.getOriginalFilename())) {
            bytes = new byte[inputStream.available()];
            inputStream.read(bytes);
            fileOutputStream.write(bytes);
            fileOutputStream.flush();
            System.out.println(new File(".").getAbsoluteFile().getAbsolutePath());
            logCost("read bytes");
        }
        catch (Exception e) {
            logger.warn("读取文件, 转成Mat对象异常", e);
        }

        Mat origin = Imgcodecs.imdecode(new MatOfByte(bytes), Imgcodecs.IMREAD_UNCHANGED);
        logCost("imdecode");

        int width = origin.width();
        int height = origin.height();

        Mat grayImg = new Mat();
        Imgproc.cvtColor(origin, grayImg, Imgproc.COLOR_RGB2GRAY);
        logCost("grayscale");

        Mat blurImg = new Mat();
        Imgproc.threshold(grayImg, blurImg, 200, 255, Imgproc.THRESH_BINARY);
        logCost("blurImg");

        Imgproc.floodFill(blurImg, new Mat(), new Point(0, 0), new Scalar(255, 255, 255));
        Imgproc.floodFill(blurImg, new Mat(), new Point(0, height -1), new Scalar(255, 255, 255));
        Imgproc.floodFill(blurImg, new Mat(), new Point(width - 1, height - 1), new Scalar(255, 255, 255));
        Imgproc.floodFill(blurImg, new Mat(), new Point(width - 1, 0), new Scalar(255, 255, 255));

        // HighGui.imshow("clearBackGround", blurImg);

        String filename = file.getOriginalFilename();
        String suffix = filename.substring(filename.lastIndexOf("."));
        String resultFile = "/home/tang/" + filename.replace(suffix, "-result" + suffix);
        Imgcodecs.imwrite(resultFile, blurImg);
        logger.info("imwrite: " + resultFile);


    }

    @PostMapping
    public long skewCorrection(MultipartFile file, @RequestParam(required = false, defaultValue = "2") int flags,
            @RequestParam(required = false, defaultValue = "1") int borderMode) {
        logger.info("收到请求");
        long start = System.currentTimeMillis();
        timestamp = start;

        // 读取字节
        byte[] bytes = null;
        try (InputStream inputStream = file.getInputStream();
                FileOutputStream fileOutputStream = new FileOutputStream(file.getOriginalFilename())) {
            bytes = new byte[inputStream.available()];
            inputStream.read(bytes);
            fileOutputStream.write(bytes);
            fileOutputStream.flush();
            logCost("read bytes");
        }
        catch (Exception e) {
            logger.warn("读取文件, 转成Mat对象异常", e);
        }

        String filename = file.getOriginalFilename();
        String suffix = filename.substring(filename.lastIndexOf("."));
        byte[] imgBytes = textSkewCorrection(bytes, suffix, flags, borderMode);

        try (FileOutputStream outputStream = new FileOutputStream(filename.replace(suffix, "-result" + suffix))) {
            outputStream.write(imgBytes);
            outputStream.flush();
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        logger.info("请求结束");

        return System.currentTimeMillis() - start;
    }

    private byte[] textSkewCorrection(byte[] imgBytes, String suffix, int flags, int borderMode) {
        if (imgBytes == null || imgBytes.length == 0) {
            return null;
        }
        long start = System.currentTimeMillis();

        Mat origin = Imgcodecs.imdecode(new MatOfByte(imgBytes), Imgcodecs.IMREAD_UNCHANGED);
        logCost("imdecode");

        int width = origin.width();
        int height = origin.height();

        Mat img = new Mat();
        Imgproc.cvtColor(origin, img, Imgproc.COLOR_RGB2GRAY);
        logCost("clone");

        Imgproc.threshold(img, img, 200, 255, Imgproc.THRESH_BINARY);
        logCost("threshold");

        Core.bitwise_not(img, img);
        Mat element = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(3, 3));
        logCost("bitwise_not");

        Imgproc.erode(img, img, element);
        logCost("erode");

        Mat wLocMat = Mat.zeros(img.size(), img.type());
        Core.findNonZero(img, wLocMat);
        logCost("findNonZero");

        MatOfPoint matOfPoint = new MatOfPoint(wLocMat);
        MatOfPoint2f mat2f = new MatOfPoint2f();
        matOfPoint.convertTo(mat2f, CvType.CV_32FC2);
        logCost("convertTo");

        RotatedRect rotatedRect = Imgproc.minAreaRect(mat2f);
        logCost("minAreaRect " + rotatedRect.angle);

        Point[] vertices = new Point[4];
        rotatedRect.points(vertices);
        List<MatOfPoint> boxContours = new ArrayList<>();
        boxContours.add(new MatOfPoint(vertices));
        Imgproc.drawContours(img, boxContours, 0, new Scalar(128, 128, 128), -1);
        logCost("drawContours " + rotatedRect.angle);

        double resultAngle = rotatedRect.angle;
        if (rotatedRect.size.width > rotatedRect.size.height) {
            resultAngle += 90.f;
        }
        logCost("resultAngle " + resultAngle);

        Point center = new Point(width / 2, height / 2);
        Mat rotImage = Imgproc.getRotationMatrix2D(center, resultAngle, 1.0);
        logCost("getRotationMatrix2D");

        //1.0 means 100 % scale
        Size size = new Size(width, height);
        Imgproc.warpAffine(origin, origin, rotImage, size, flags, borderMode);
        logCost("warpAffine");

        MatOfByte matOfByte = new MatOfByte();
        suffix = suffix.startsWith(".") ? suffix : "." + suffix;
        Imgcodecs.imencode(suffix, origin, matOfByte);
        byte[] byteArray = matOfByte.toArray();

        // Imgcodecs.imwrite("/home/tang/18_correct.jpg", origin);
        // logCost("imwrite");

        logger.info("处理耗时: {}", System.currentTimeMillis() - start);
        return byteArray;
    }

    private void logCost(String mark) {
        long now = System.currentTimeMillis();
        logger.info(mark + ": {}", now - timestamp);
        timestamp = now;
    }

    @GetMapping(produces = MediaType.IMAGE_JPEG_VALUE)
    public byte[] opencv(String filename, int flag, int borderMode) {
        computeSkew("/home/tang/" + filename, flag, borderMode);

        try (FileInputStream inputStream = new FileInputStream("/home/tang/18_correct.jpg")) {
            byte[] bytes = new byte[inputStream.available()];
            inputStream.read(bytes);
            // return return_buff;
            return bytes;
        }
        catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    // https://stackoverflow.com/questions/41608832/how-to-skew-angle-correction-using-java
    public Mat computeSkew(String inFile, int flags, int borderMode) {
        logger.info("start");
        //Load this image in grayscale
        Mat img = Imgcodecs.imread(inFile, Imgcodecs.IMREAD_GRAYSCALE);

        Mat orgImg = img.clone();
        int width = orgImg.width();
        int height = orgImg.height();
        logger.info("read");

        //Binarize it
        //Use adaptive threshold if necessary
        //Imgproc.adaptiveThreshold(img, img, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 15, 40);
        Imgproc.threshold(img, img, 200, 255, Imgproc.THRESH_BINARY);
        logger.info("threshold");

        //Invert the colors (because objects are represented as white pixels, and the background is represented by
        // black pixels)
        Core.bitwise_not(img, img);
        Mat element = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(3, 3));
        logger.info("bitwise_not");

        //We can now perform our erosion, we must declare our rectangle-shaped structuring element and call the erode
        // function
        Imgproc.erode(img, img, element);
        logger.info("erode");

        //Find all white pixels
        Mat wLocMat = Mat.zeros(img.size(), img.type());
        Core.findNonZero(img, wLocMat);
        logger.info("findNonZero");

        //Create an empty Mat and pass it to the function
        MatOfPoint matOfPoint = new MatOfPoint(wLocMat);

        //Translate MatOfPoint to MatOfPoint2f in order to user at a next step
        MatOfPoint2f mat2f = new MatOfPoint2f();
        matOfPoint.convertTo(mat2f, CvType.CV_32FC2);
        logger.info("convertTo");

        //Get rotated rect of white pixels
        RotatedRect rotatedRect = Imgproc.minAreaRect(mat2f);
        logger.info("minAreaRect " + rotatedRect.angle);

        Point[] vertices = new Point[4];
        rotatedRect.points(vertices);
        List<MatOfPoint> boxContours = new ArrayList<>();
        boxContours.add(new MatOfPoint(vertices));
        Imgproc.drawContours(img, boxContours, 0, new Scalar(128, 128, 128), -1);
        logger.info("drawContours " + rotatedRect.angle);

        double resultAngle = rotatedRect.angle;
        if (rotatedRect.size.width > rotatedRect.size.height) {
            resultAngle += 90.f;
        }

        //Or
        //rotatedRect.angle = rotatedRect.angle < -45 ? rotatedRect.angle + 90.f : rotatedRect.angle;

        Point center = new Point(width / 2, height / 2);
        Mat rotImage = Imgproc.getRotationMatrix2D(center, resultAngle, 1.0);
        logger.info("getRotationMatrix2D");

        //1.0 means 100 % scale
        Size size = new Size(width, height);
        Imgproc.warpAffine(orgImg, orgImg, rotImage, size, flags, borderMode);

        Imgcodecs.imwrite("/home/tang/18_correct.jpg", orgImg);
        logger.info("imwrite");

        return orgImg;
    }

}
